#include "CALevel.h"
#include "Tile.h"
#include "Wall.h"
#include "Util.h"
//#include <iostream>

CALevel::CALevel(int x, int y, int levelNum, int dlevel, Dungeon* dungeon) : Level(x, y, levelNum, dlevel, dungeon) {
	generateLevel();
	placePortal(true, 0);
}

CALevel::CALevel(int x, int y, Level* level) : Level(x, y, level) {
	generateLevel();
	for(int i = 0; i < level->getNumPortals(false); i++) {
		placePortal(true, i);
	}
}

CALevel::~CALevel() {
}

void CALevel::generateLayout() {
	// initialize tile map with normal tiles
	m_map = new Tile**[m_cols];
	int i, j, k, x, y;
	for(i = 0; i < m_cols; i++) {
		m_map[i] = new Tile*[m_rows];
		for(j = 0; j < m_rows; j++) {
			m_map[i][j] = new Tile(TT_NORMAL, float(i), float(j), this);
		}
	}
	
	// intiialize 3D obstacle array
	// first col - x
	// second col - y
	// third col - current iteration value vs previous iteration value
	// value - obstacle type
	//   true = wall
	//   false = floor
	// current and previous iteration values are necessary because if
	// you change the value of a tile from non-wall to wall and only
	// store the current values, the next now might have 5 wall neighbors
	// instead of 4, which means it would get a wall when it really
	// shouldn't have
	bool*** walls = new bool**[m_cols];
	for(i = 0; i < m_cols; i++) {
		walls[i] = new bool*[m_rows];
		for(j = 0; j < m_rows; j++) {
			walls[i][j] = new bool[2];
			// 45% chance to tiles to start with a wall
			walls[i][j][0] = walls[i][j][1] = x_in_y(45, 100);
		}
	}

	// all tiles on the map edge must have walls
	for(i = 0; i < m_rows; i++) {
		walls[0][i][0] = walls[0][i][1] = true;
		walls[m_cols-1][i][0] = walls[m_cols-1][i][1] = true;
	}
	for(i = 1; i < m_cols-1; i++) {
		walls[i][0][0] = walls[i][0][1] = true;
		walls[i][m_rows-1][0] = walls[i][m_rows-1][1] = true;
	}

	// between 3 and 10 CA iterations
	// magic numbers!
	int numIterations = x_between(3,10);
	for(i = 0; i < numIterations; i++) {
		// iterate over each tile for each iteration
		for(j = 1; j < m_cols-1; j++) {
			for(k = 1; k < m_rows-1; k++) {
				int numNeighbors = 0;
				// count how many neighbor tiles have walls
				for(x = j-1; x <= j+1; x++) {
					for(y = k-1; y <= k+1; y++) {
						if(walls[x][y][0])
							numNeighbors++;
					}
				}
				// if 5 or more neighbors have walls, this one now has a wall too
				walls[j][k][1] = numNeighbors >= 5;
			}
		}
		// once an iteration is finished, copy "current" wall values to "previous" values
		for(j = 1; j < m_cols-1; j++) {
			for(k = 1; k < m_rows-1; k++) {
				walls[j][k][0] = walls[j][k][1];
			}
		}
	}

	// once it's over, set all the "previous" values to false, we're hijacking
	// this field for the bucketfill algorithm
	for(j = 0; j < m_cols-1; j++) {
		for(k = 0; k < m_rows-1; k++) {
			walls[j][k][1] = false;
		}
	}

	// in order to ensure all sections of the level are connect, simply
	// find an empty tile near the middle of the level and draw hallways
	// from each unconnected chunk to that tile
	// first, find an empty tile near the middle of the level
	x = m_cols/2;
	y = m_rows/2;
	int modX = 0, modY = 0;
	bool emptyTile = false;
	while(!emptyTile) {
		for(i = x-modX; i <= x+modX && !emptyTile; i++) {
			for(j = y-modY; j <= y+modY; j++) {
				if(walls[i][j][0] == false) {
					emptyTile = true;
					x = i;
					y = j;
					break;
				}
			}
		}
		modX++;
		modY++;
	}

	// bucketfill on the middle tile, this is the "core" of the level
	bucketFill(walls, x, y);
	emptyTile = false;
	// check all tiles to see if they are a floor AND they haven't been bucketfilled
	// if so, they need to be connected
	for(i = 1; i < m_cols-1; i++) {
		for(j = 1; j < m_rows-1; j++) {
			if(walls[i][j][0] == false && walls[i][j][1] == false) {
				int currX = i, currY = j;
				// carve a hallway from the disconnected tile towards the center tile
				// stopping as soon as a connected tile is hit
				while((currX != x || currY != y) && !emptyTile) {
					// choose a random direction (horizontal or vertical)
					if(one_in(2) && currX != x) {
						// for this hallway segment, pick a random length from 1 to distance between start and end
						int distance = rand_plus_one(int(std::abs(double(currX-x))));
						// set walls to floor
						for(k = 0; k < distance; k++) {
							if(walls[currX][currY][0] == false && walls[currX][currY][1] == true) { // stop if connected tile is found
								emptyTile = true;
								break;
							}
							walls[currX][currY][0] = false;
							if(currX < x)
								currX++;
							else
								currX--;
						}
					} else if(currY != y) {
						int distance = rand_plus_one(int(std::abs(double(currY-y))));
						for(k = 0; k < distance; k++) {
							if(walls[currX][currY][0] == false && walls[currX][currY][1] == true) {
								emptyTile = true;
								break;
							}
							walls[currX][currY][0] = false;
							if(currY < y)
								currY++;
							else
								currY--;
						}
					}
				}
				emptyTile = false;
				// rerun bucketfill to include all the newly connected tiles
				bucketFill(walls, i, j);
			}
		}
	}
	// print ASCII representation to ocnsole
	/*for(j = m_rows-1; j >= 0; j--) {
		for(i = 0; i < m_cols; i++) {
			if(walls[i][j][0])
				std::cout << "#";
			else
				std::cout << ".";
		}
		std::cout << std::endl;
	}*/

	// actually add the obstacles to the tiles
	for(i = 0; i < m_cols; i++) {
		for(j = 0; j < m_rows; j++) {
			if(walls[i][j][0]) {
				m_map[i][j]->addObstacle(new Wall());
			}
		}
	}

	// free up all that memory
	for(i = 0; i < m_cols; i++) {
		for(j = 0; j < m_rows; j++)
			delete[] walls[i][j];
		delete[] walls[i];
	}
	delete[] walls;
}

void CALevel::bucketFill(bool*** walls, int x, int y) {
	// only bucketfill if tile is a floor and it hasn't already been filled
	if(walls[x][y][0] == false && walls[x][y][1] == false) {
		walls[x][y][1] = true;
		for(int i = x-1; i <= x+1; i++) {
			for(int j = y-1; j <= y+1; j++) {
				if(i != x || j != y && i > 0 && i < m_cols-1 && j > 0 && j < m_rows-1)
					bucketFill(walls, i, j); // run recursively on 4 cardinal neighbors
			}
		}
	}
}